{------------------------------------------------------------------------------
#
#	Apple Macintosh Developer Technical Support
#
#	"Skippy White's Famous High Level Off-Screen Map RoutinesÓ
#
#	Offscreen.inc1.p	-	Pascal Source
#
#	Copyright © 1989 Apple Computer, Inc.
#	All rights reserved.
#	The characters depicted herein (except Skippy White) are fictitious.
#
#	Versions:	
#				1.00				04/89
#
#	Components:	
#				Offscreen.p			April 1, 1988
#				Offscreen.inc1.p	April 1, 1988
#
#	These routines provide a high-level interface to the QuickDraw & Color
#	Manager routines which allow the creation and manipulation of off-screen
#	bitmaps and pixmaps. They are designed to run on any machine with 128K or
#	later ROMs (sorry 64K ROM fans).
#	Note that the design incorporates the idea that you can go along pretending
#	there is an offscreen buffer even when one couldnÕt be allocated, and the
#	calls will do nothing.
#
------------------------------------------------------------------------------}

CONST
	noDepth = -2;
	chunky	= 0;

TYPE
	PrivateHandle	= ^PrivatePtr;
	PrivatePtr		= ^PrivateRecord;
	PrivateRecord	= RECORD
		requestedBounds	: Rect; {boundary rectangle for the pixmap}
		{used to determine the depth, size, and dimensions of the pixmap}

		requestedDepth	: INTEGER; {the requested depth of this map}
		{if > 0, a private device will be created for the map}

		requestedColors	: CTabHandle; {color table}
		{our own copy of the color table that was passed in}

		requestedPolite	: BOOLEAN; {whether he wanted it polite}

		bits			: Handle; {handle to the off-screen bits}
		{if NIL, there is no port or graphics device either}
		notOurs			: BOOLEAN; {TRUE means that we didnÕt create the bits}
		needs32Bits		: BOOLEAN; {you must be in 32 bit memory mode to get to bits}

		bitsPort		: CGrafPtr; {GrafPort used for the off-screen map.}
		bitsDevice		: GDHandle; {GDevice associated with the map}
		{private device (if requestedDepth > 0)}
		{(from the device list for requestedDepth = kMaxDepth)}

		oldSeed			: LONGINT; {old CTSeed for the pixmap's color table}
		{used to detect color updates}
		
		invalRegion		: RgnHandle; {part of offscreen buffer that is invalid}

		drawingPort		: GrafPtr; {supplied to BeginOffscreenDahling}
		savedPort		: GrafPtr; {saves old GrafPort while drawing off screen}
		savedMap		: BitMap; {used to save map when we donÕt use our port}
		savedVisRgn		: RgnHandle; {saves the visRgn from a user-supplied port}
		savedDevice		: GDHandle; {saves old GDevice while drawing off screen}
	END; {PrivateRecord}



VAR
	theMac	: SysEnvRec;


{---------- low-level service routines used by the rest of Offscreen ----------}

{Fill out the pixmap according to the bounds, pixelType, pixelSize, cmpCount,
 cmpSize and pmTable parameters. All fields will be modified.
 The initial pixmap will be copied from the current GDeviceÕs pixmap, so you must modify
 the pmap^^.baseAddr before using it.
 dataSize will return with the number of bytes required for the pixel data.
 Note: it is the responsibility of the caller to fill in the baseAddr field
 of the pixmap. You may want to call NewImprovedGBuffer to see if the system will
 allocate the data space and then set baseAddr to point to it.
 This call cannot fail.}
PROCEDURE InitGBufferPixmap(pmap: PixMapHandle; aBounds: Rect; aPixelType, aPixelSize,
 	aCmpCount, aCmpSize: INTEGER; aPMTable: CTabHandle; VAR dataSize: LONGINT);
	
VAR
	theGDevice:	GDHandle;
BEGIN
	theGDevice := GetGDevice;				{get the current device}
	pmap^^:=theGDevice^^.gdPMap^^;			{start out with deviceÕs pixmap}
	WITH pmap^^ DO BEGIN
		bounds := aBounds;
		pixelType := aPixelType;
		pixelSize := aPixelSize;
 		cmpCount := aCmpCount;
		cmpSize := aCmpSize;
		pmTable := aPMTable;
		WITH bounds DO BEGIN
			rowBytes := ((pixelSize * right + 15) DIV 16) * 2;
			{calculate an even # of words}
			dataSize := bottom * LONGINT(rowBytes); {calculate the size}
			rowBytes := rowBytes + $8000;	{flag that itÕs a pixmap}
		END; {WITH}
	END; {WITH}
END; {InitGBufferPixmap}
	
{Fill out a pixmap for use with an onscreen grafPort. It differs from InitGBufferPixmap
 in that it associates the buffer with an onscreen device. This will be the maximim-
 depth device which intersects the globalBounds rectangle. In the most common case
 you will pass in the portRect of a window after converting it to global coordinates.
 If no screen device intersects globalBounds, the function will return FALSE and
 device will return with NIL. If it returns TRUE then device will be a handle to
 the proper GDevice (see StartLeech, below).
 For the other information, see the comments at InitGBufferPixmap.}
FUNCTION InitGBufferScreen(pmap: PixMapHandle; globalBounds: Rect; VAR device:GDHandle;
 	 VAR dataSize: LONGINT): BOOLEAN;

VAR
	savedDevice:	GDHandle;
BEGIN
	InitGBufferScreen := FALSE;
	
	device := GetMaxDevice(globalBounds);		{maximum-depth device for globalBounds}
	IF device <> NIL THEN BEGIN
		savedDevice := GetGDevice;
		
		WITH globalBounds DO
			SetRect(globalBounds, 0, 0, right - left, bottom - top);
		{make globalBounds into a window-ish (zero-based) rectangle}
			
		SetGDevice(device);						{set to device for InitGBufferPixmap}
		WITH device^^.gdPMap^^ DO
			InitGBufferPixmap(pmap, globalBounds, pixelType, pixelSize,
 				cmpCount, cmpSize, pmTable, dataSize);
			{set up the pixmap}
			
		SetGDevice(savedDevice);
		InitGBufferScreen := TRUE;
	END; {IF device <> NIL}
END; {InitGBufferScreen}

{Allocate the data space for the bits of an offscreen buffer. Only do so if
 it can be allocated in an optimum place for graphics use. If the system
 doesnÕt have a superior place to allocate the bits, NIL is returned.
 needs32Bits will return TRUE if the bit space requires 32 bit addressing
 in order to access it. buffNotNeeded will return TRUE if performance will be
 improved by NOT buffering (ÒperformanceÓ means avoiding flicker, etc.)}
FUNCTION NewImprovedGBuffer(dataSize: LONGINT; VAR needs32Bits: BOOLEAN;
			VAR buffNotNeeded: BOOLEAN): Ptr;

BEGIN
	NewImprovedGBuffer := NIL;
	needs32Bits := FALSE;
	buffNotNeeded := FALSE;
END; {NewImprovedGBuffer}

{Free a buffer allocated via NewImprovedGBuffer.}
PROCEDURE FreeEnhancedGBuffer(bits: Ptr);

BEGIN
END; {FreeEnhancedGBuffer}

{Fill out the GDevice record and set device^^.gdPMap = pixmap. Errors can occur from not
 being able to build the ITable, etc.}
FUNCTION InitGBufferDevice(device: GDHandle; pmap: PixMapHandle): OSErr;

VAR
	preferredResolution:	INTEGER;
	inverseTable: 			ITabHandle;

	PROCEDURE Out(error: OSErr);
	BEGIN {Out}
		IF error <> noErr THEN BEGIN
			DisposHandle(Handle(device^^.gdITable));	{get rid of the inverse table}
			InitGBufferDevice := error;
			EXIT(InitGBufferDevice);
		END; {IF error <> noErr}
	END; {Out}

BEGIN
	device^^.gdITable := NIL;
	inverseTable := ITabHandle(NewHandle(0));	{create the inverse table stub}
	Out(MemError);								{bail if couldnÕt make the handle}
	MakeITable(pmap^^.pmTable, inverseTable, preferredResolution);
	Out(QDError);								{bail if MakeITable failed}
	
	preferredResolution := GetMainDevice^^.gdResPref; {preferred resolution ??? is this cool?}
	
	WITH device^^ DO BEGIN
		gdType := clutType;						{no private devices for direct ones, right?}	
		gdITable := inverseTable;
		gdResPref := preferredResolution;
		gdFlags := 2 ** noDriver;
		gdPMap := pmap;
		gdRect := pmap^^.bounds;
	END; {WITH}
	
	InitGBufferDevice := noErr;
END; {InitGBufferDevice}


{---------- end of low-level service routines ----------}


PROCEDURE InitOffscreen;
{128K ROMs and new enough Palette Manager}

VAR
	error	: OSErr;

BEGIN {InitOffscreen}
	error := SysEnvirons(1, theMac);
END; {InitOffscreen}


(* ??? commented out since we no longer use it (but someone may want it, so should we provide it as a utility?)
FUNCTION GetMaxAreaDevice(globalRect: Rect): GDHandle;
{Find the greatest overlap device for the given global rectangle.}

VAR
	area			: LONGINT;
	maxArea			: LONGINT;
	device			: GDHandle;
	intersection	: Rect;

BEGIN {GetMaxAreaDevice}
	GetMaxAreaDevice := NIL;

	maxArea := 0;

	device := GetDeviceList;
	WHILE device <> NIL DO BEGIN
		IF TestDeviceAttribute(device, screenDevice) THEN
			IF TestDeviceAttribute(device, screenActive) THEN
				IF SectRect(globalRect, device^^.gdRect, intersection) THEN BEGIN
					WITH intersection DO
						area := LONGINT(right - left) * LONGINT(bottom - top);
					IF area > maxArea THEN BEGIN
						GetMaxAreaDevice := device;
						maxArea := area;
					END; {IF area > maxArea}
				END; {IF SectRect...}
		device := GetNextDevice(device);
	END; {WHILE device <> NIL}
END; {GetMaxAreaDevice}
*)



FUNCTION MakeCleanColorTable(colors: CTabHandle; depth: INTEGER;
							 VAR table: CTabHandle): OSErr;
{This call takes a handle to a list of colors to be included in a color table.
 It returns a table suitable for use for a PixMap, with black and white at the
 correct offsets. If there is no default color table for the depth in question,
 it returns NIL for the table.}

	FUNCTION IsWhite(rgb: RGBColor): BOOLEAN;
	BEGIN {IsWhite}
		IsWhite := (rgb.red = $FFFF) & (rgb.green = $FFFF) & (rgb.blue = $FFFF);
	END; {IsWhite}

	FUNCTION IsBlack(rgb: RGBColor): BOOLEAN;
	BEGIN {IsBlack}
		IsBlack := (rgb.red = 0) & (rgb.green = 0) & (rgb.blue = 0);
	END; {IsBlack}

VAR
	tableIndex	: INTEGER;
	colorIndex	: INTEGER;
	aColor		: RGBColor;

BEGIN {MakeCleanColorTable}
	table := GetCTable(depth);
	IF table = NIL THEN
		MakeCleanColorTable := memFullErr
	ELSE
		WITH table^^ DO BEGIN
			ctSeed := GetCTSeed; {get a fresh seed; we'll change the color table}

			tableIndex := 1; {skip white in table we are making}
			colorIndex := 0; {start with first color}

			REPEAT
				IF colorIndex > colors^^.ctSize THEN
					Leave; {done with all the colors}
				aColor := colors^^.ctTable[colorIndex].rgb;
				colorIndex := colorIndex + 1; {advance to next color}
				IF IsWhite(aColor) THEN
					Cycle; {this one is white, try another}
				IF IsBlack(aColor) THEN
					Cycle; {this one is black, try another}

		{$PUSH} {$R-}
				ctTable[tableIndex].rgb := aColor; {use this color}
		{$POP}
				tableIndex := tableIndex + 1;
			UNTIL tableIndex > ctSize - 1; {until the table is full}

			MakeCleanColorTable := noErr;
		END; {WITH}
END; {MakeCleanColorTable}



PROCEDURE DeallocateBits(offscreenHandle: Handle);
{Get rid of the bits from an off-screen map.}

BEGIN {DeallocateBits}
	IF offscreenHandle <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
			IF NOT notOurs THEN
				DisposHandle(bits) {get rid of the bits}
			ELSE
				FreeEnhancedGBuffer(Ptr(bits));
			bits := NIL;

			IF bitsPort <> NIL THEN BEGIN
				IF bitsPort^.visRgn <> NIL THEN BEGIN {visRgn of NIL means port is not open yet}
					IF requestedDepth > 0 THEN {if we created a private device and color table}
						DisposHandle(Handle(bitsPort^.portPixMap^^.pmTable)); {get rid of the colors}
					ClosePort(GrafPtr(bitsPort));
			
				DisposeRgn(invalRegion); {get rid of the invalidation region}
				END; {IF ...bitsPort^.visRgn <> NIL}
				DisposPtr(Ptr(bitsPort));
			END; {IF bitsPort <> NIL}
			bitsPort := NIL;

			IF bitsDevice <> NIL THEN
				IF requestedDepth > 0 THEN BEGIN {if we created a private device}
					bitsDevice^^.gdPMap := NIL; {the pixmap was owned by the port}
					DisposGDevice(bitsDevice);
					bitsDevice := NIL;
				END; {IF requestedDepth > 0}
		END; {WITH}
END; {DeallocateBits}



FUNCTION GetMap(offscreenHandle: Handle): BitMapPtr;
{Get a BitMapPtr for the bits in the offscreen handle.
 This is also the best way to see if the bits are allocated.
 Note that this has a side-effect of setting up the baseAddr properly.}

VAR
	map	: BitMapPtr;

BEGIN {GetMap}
	map := NIL;

	IF offscreenHandle <> NIL THEN {if we have bits}
		WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
			IF bits <> NIL THEN {if we seem to have bits, make sure that we really do}
				IF StripAddress(bits^) = NIL THEN
					DeallocateBits(offscreenHandle)
				ELSE BEGIN
					{find the map}
					WITH bitsPort^ DO
						IF portVersion > 0 THEN {old-style port?}
							map := @portPixMap {this is really portBits!}
						ELSE
							map := BitMapPtr(portPixMap^);
					IF notOurs THEN
						map^.baseAddr := Ptr(bits)
					ELSE
						map^.baseAddr := bits^;
				END; {ELSE}
		END; {WITH}
	GetMap := map;
END; {GetMap}



FUNCTION GetBitsHandle(offscreenHandle: Handle): Handle;
BEGIN
	GetBitsHandle := NIL;
	IF offscreenHandle <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO
			IF NOT notOurs THEN
				GetBitsHandle := bits;
END; {GetBitsHandle}



FUNCTION AllocateBits(offscreenHandle: Handle; VAR buffNotNeeded: BOOLEAN): OSErr;
{Allocate the bits, port, device, etc. for the off-screen map.
 If an error code is returned, the handle will be in the "dormant" stage. ??? define
 If the bits are already allocated, then nothing will happen.}

VAR
	oldPort				: GrafPtr;
	bounder				: Rect;
	map					: BitMapPtr;
	bitsSize			: LONGINT;
	colors				: CTabHandle;
	preferredResolution	: INTEGER;
	inverseTable		: ITabHandle;
	totalSpace			: LONGINT;
	contiguousSpace		: LONGINT;
	use32Bits			: BOOLEAN;

	PROCEDURE Out(error: OSErr);
	BEGIN {Out}
		IF error <> noErr THEN BEGIN
			HUnlock(offscreenHandle);
			DeallocateBits(offscreenHandle);
			SetPort(oldPort);
			AllocateBits := error;
			EXIT(AllocateBits);
		END; {IF error <> noErr}
	END; {Out}

BEGIN {AllocateBits}
	IF offscreenHandle <> NIL THEN
		IF GetMap(offscreenHandle) = NIL THEN BEGIN {if the bits are not already allocated}
			GetPort(oldPort);

			MoveHHi(offscreenHandle); {fly the handle high in the heap since we lock it down}
			HLock(offscreenHandle);
			WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
				bitsPort := CGrafPtr(NewPtrClear(SIZEOF(GrafPort))); {create a new port}
				{NewPtrClear means that bits := NIL, invalRegion := NIL, notOurs := FALSE, etc.}
				Out(MemError);

				WITH requestedBounds DO
					SetRect(bounder, 0, 0, right - left, bottom - top);

				IF theMac.hasColorQD THEN
					WITH bitsPort^ DO BEGIN
						OpenCPort(bitsPort); {always use a color port}
						portPixMap^^.pmTable := NIL; {in case we fail before we make a color table}
						
						IF requestedDepth > 0 THEN BEGIN {if there is a private device}
							{make a color table from the requested colors or a resource file or ROM}
							Out(MakeCleanColorTable(requestedColors, requestedDepth, colors));
							
							{now set up the pixmap of the port}
							InitGBufferPixmap(portPixMap, bounder, chunky, requestedDepth, 1, requestedDepth,
								colors, {returns} bitsSize);
		
							{make a private device}
							bitsDevice := GDHandle(NewHandleClear(SIZEOF(GDevice)));
							Out(MemError); {must have failed because of heap paucity}
		
							inverseTable := ITabHandle(NewHandle(0)); {create the inverse table}
							Out(MemError);
							preferredResolution := GetMainDevice^^.gdResPref; {preferred resolution ??? is this cool?}
		
							WITH bitsDevice^^ DO BEGIN {set up the other fields of the device}
								gdType := clutType;
								gdITable := inverseTable;
								gdResPref := preferredResolution;
								gdFlags := 2 ** noDriver;
								gdPMap := portPixMap;
							END; {WITH}
							InitGDevice(0, -1, bitsDevice); {sets gdRect from pixmap now; in future may do more}
							MakeITable(colors, inverseTable, preferredResolution);
							Out(QDError); {MakeITable failed}
						END ELSE BEGIN {when there is no private device}
								{get the max. area device and set up the portÕs pixmap accordingly}
								IF NOT InitGBufferScreen(portPixMap, requestedBounds, bitsDevice, {returns} bitsSize) THEN
									Out(noDeviceIntersectErr); {no devices intersect bounds}
									
								oldSeed := portPixMap^^.pmTable^^.ctSeed; {used to detect color updates}
						END;
						
					map := BitMapPtr(portPixMap^); {point to the map so we can set the portRect & visRgn}

{!!! ??? Do we need to call ForeColor and BackColor on the CGrafPort to set it up right?}

					END {WITH}
				ELSE BEGIN {Classic QuickDraw}
					OpenPort(GrafPtr(bitsPort));
					WITH GrafPtr(bitsPort)^, portBits DO BEGIN
						bounds := bounder;
						{figure out how much space we need for our bits}
						WITH bounds DO BEGIN
							rowBytes := ((right + 15) DIV 16) * 2;
							{calculate an even # of words}
							bitsSize := bottom * LONGINT(rowBytes); {calculate the size}
						END; {WITH}
						map := @portBits; {point to the map so we can set it up}
					END; {WITH}
				END;
					
				WITH bitsPort^ DO BEGIN
					portRect := map^.bounds; {port's portRect is same as bounds}
					RectRgn(visRgn, portRect); {the visRgn must be changed}
					invalRegion := NewRgn;
					Out(MemError);
					CopyRgn(visRgn, invalRegion);
				END; {WITH}
				
				bits := Handle(NewImprovedGBuffer(bitsSize, needs32Bits, buffNotNeeded));
				notOurs := bits <> NIL;
				IF NOT notOurs THEN BEGIN
					IF requestedPolite THEN
						contiguousSpace := MaxBlock {make sure we have room without purging}
					ELSE
						PurgeSpace(totalSpace, contiguousSpace); {make sure we have room with purging}
					IF bitsSize + kOffscreenReserve > contiguousSpace THEN
						{if there is not enough room for the bits & reserve block, error out}
						Out(memFullErr);
					bits := NewHandleClear(bitsSize); {get space for the bits}
					Out(MemError);
					IF requestedPolite THEN
						HPurge(bits); {keep bits purgeable if we are polite}
				END;
				
			END; {WITH}
			HUnlock(offscreenHandle);

			SetPort(oldPort);
		END; {IF GetMap(offscreenHandle) = NIL}

	AllocateBits := noErr;
END; {AllocateBits}



PROCEDURE DisposeOffscreen(offscreenHandle: Handle);
{Get rid of everything, including offscreenHandle itself.}

BEGIN {DisposeOffscreen}
	IF offscreenHandle <> NIL THEN BEGIN
		DeallocateBits(offscreenHandle);
		DisposHandle(Handle(PrivateHandle(offscreenHandle)^^.requestedColors));
		DisposHandle(offscreenHandle);
	END; {IF offscreenHandle <> NIL}
END; {DisposeOffscreen}



FUNCTION NewOffscreen(bounds: Rect; depth: INTEGER; colors: CTabHandle;
					  memoryPolite: BOOLEAN; VAR buffNotNeeded: BOOLEAN; VAR offscreenHandle: Handle): OSErr;
{Create an offscreenHandle. Return NIL if there is an error.}

VAR
	aHandle	: Handle;

	PROCEDURE Out(error: OSErr);
	BEGIN {Out}
		IF error <> noErr THEN BEGIN
			DisposHandle(aHandle); {dispose handle if we made it}
			NewOffscreen := error;
			offscreenHandle := NIL;
			EXIT(NewOffscreen);
		END; {IF error <> noErr}
	END; {Out}

BEGIN {NewOffscreen}
	aHandle := NewHandleClear(SizeOf(PrivateRecord)); {make the private block}
	Out(MemError);

	MoveHHi(aHandle); {fly this high so it doesn't fragment the heap}
	HLock(aHandle);
	WITH PrivateHandle(aHandle)^^ DO BEGIN
		requestedBounds := bounds;
		IF theMac.hasColorQD THEN
			requestedDepth := depth
		ELSE
			requestedDepth := noDepth;
		IF requestedDepth > 0 THEN BEGIN {if we need a private device}
			Out(HandToHand(Handle(colors)));
			requestedColors := colors;
		END;
		requestedPolite := memoryPolite;
	END; {WITH}
	HUnlock(aHandle);

	{now, we have filled in all of the fields of offscreenHandle}
	NewOffscreen := AllocateBits(aHandle, buffNotNeeded);
	offscreenHandle := aHandle;
END; {NewOffscreen}



FUNCTION CheckOffscreen(offscreenHandle: Handle; VAR drawNeeded: BOOLEAN): OSErr;

VAR
	error			: OSErr;
	newSeed			: LONGINT;
	buffNotNeeded	: BOOLEAN;

BEGIN {CheckOffscreen}
	drawNeeded := TRUE; {default we must draw if offscreen invalid}
	error := noErr; {no error either}
	IF offscreenHandle <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
			IF requestedDepth = kMaxDepth THEN {if it's not a private device}
				IF oldSeed <> bitsDevice^^.gdPMap^^.pmTable^^.ctSeed THEN {we have a color update, Houston}
					DeallocateBits(offscreenHandle);

			drawNeeded := GetMap(offscreenHandle) = NIL;
			error := AllocateBits(offscreenHandle, buffNotNeeded); {allocate if deallocated}
		END; {WITH}
			
	IF GetMap(offscreenHandle) <> NIL THEN
		drawNeeded := drawNeeded | (NOT EmptyRgn(PrivateHandle(offscreenHandle)^^.invalRegion));
		{canÕt use the width for invalRegion since we may have done an AllocateBits}
		
	CheckOffscreen := error;
END; {CheckOffscreen}


PROCEDURE ValidRectOffscreen(offscreenHandle: Handle; window: WindowPtr; validRect: Rect);

VAR
	validRgn	: RgnHandle;

BEGIN
	validRgn := NewRgn;
	RectRgn(validRgn, validRect);
	ValidRgnOffscreen(offscreenHandle, window, validRgn);
	DisposeRgn(validRgn);
END; {ValidRectOffscreen}


PROCEDURE ValidRgnOffscreen(offscreenHandle: Handle; window: WindowPtr; validRegion: RgnHandle);

VAR
	savedPort	: GrafPtr;
	
BEGIN
	IF window <> NIL THEN BEGIN
		GetPort(savedPort);
		SetPort(window);
		ValidRgn(validRegion);
		SetPort(savedPort);
	END;
	IF GetMap(offscreenHandle) <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO
			DiffRgn(invalRegion, validRegion, invalRegion);
END; {ValidRgnOffscreen}


PROCEDURE InvalRectOffscreen(offscreenHandle: Handle; window: WindowPtr; invalidRect: Rect);

VAR
	invalidRgn	: RgnHandle;

BEGIN
	invalidRgn := NewRgn;
	RectRgn(invalidRgn, invalidRect);
	InvalRgnOffscreen(offscreenHandle, window, invalidRgn);
	DisposeRgn(invalidRgn);
END; {InvalRectOffscreen}


PROCEDURE InvalRgnOffscreen(offscreenHandle: Handle; window: WindowPtr; invalidRgn: RgnHandle);

VAR
	savedPort	: GrafPtr;
	
BEGIN
	IF window <> NIL THEN BEGIN
		GetPort(savedPort);
		SetPort(window);
		InvalRgn(invalidRgn);
		SetPort(savedPort);
	END;
	IF GetMap(offscreenHandle) <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO
			UnionRgn(invalRegion, invalidRgn, invalRegion);
END; {ValidRgnOffscreen}


FUNCTION CheckBoundsOffscreen(offscreenHandle: Handle; newBounds: Rect;
					   VAR drawNeeded: BOOLEAN): OSErr;

VAR
	somethingChanged	: BOOLEAN;
	rightDevice			: GDHandle;

BEGIN {CheckBoundsOffscreen}
	IF offscreenHandle <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
			{check to see if the size of the port changed}
			WITH newBounds, bitsPort^.portRect.botRight DO
				somethingChanged := (right - left <> h) | (bottom - top <> v);

			IF NOT somethingChanged THEN BEGIN
				{check to see if the depth of the screen changed}
				rightDevice := NIL;
				IF requestedDepth = kMaxDepth THEN
					rightDevice := GetMaxDevice(newBounds);
				somethingChanged := (rightDevice <> NIL) & (rightDevice <> bitsDevice);
			END;

			IF somethingChanged THEN BEGIN {we have a color update, Houston}
				DeallocateBits(offscreenHandle);
				requestedBounds := newBounds;
			END;
		END; {WITH}
	CheckBoundsOffscreen := CheckOffscreen(offscreenHandle, drawNeeded);
END; {CheckBoundsOffscreen}



PROCEDURE BeginOffscreenDrawing(offscreenHandle: Handle; port: GrafPtr);
BEGIN {BeginOffscreenDrawing}
	IF GetMap(offscreenHandle) <> NIL THEN
	{note side effect here of setting up map for drawing (stuffing baseAddr)}
		WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
			IF NOT notOurs THEN {if we, not the system, allocated the bits}
				{lock the bits down}
				HLock(bits);

			{we need the pixmap locked while we are using it}
			IF theMac.hasColorQD THEN
				HLock(Handle(bitsPort^.portPixMap));

			drawingPort := port;
			GetPort(savedPort);
			
			{set up the port and the device}
			
			IF drawingPort = NIL THEN
				SetPort(GrafPtr(bitsPort))
			ELSE BEGIN
				SetPort(drawingPort);
				{save the visRgn and replace it with our private one}
				savedVisRgn := drawingPort^.visRgn;
				drawingPort^.visRgn := bitsPort^.visRgn;
				
				savedMap := drawingPort^.portBits;
				
				IF theMac.hasColorQD THEN
					SetPortPix(bitsPort^.portPixMap)
				ELSE
					SetPortBits(GrafPtr(bitsPort)^.portBits);
			END; {for caller-supplied port case}
			
			IF theMac.hasColorQD THEN BEGIN
				savedDevice := GetGDevice;
				SetGDevice(bitsDevice);
			END; {IF theMac.hasColorQD}
			
			WITH thePort^ DO
				{intersect the invalidated offscreen region with private visRgn}
				SectRgn(invalRegion, visRgn, visRgn);
		END; {WITH}
END; {BeginOffscreenDrawing}



PROCEDURE EndOffscreenDrawing(offscreenHandle: Handle);

TYPE
	PMapHandlePtr	= ^PixMapHandle;
	
BEGIN {EndOffscreenDrawing}
	IF GetMap(offscreenHandle) <> NIL THEN
		WITH PrivateHandle(offscreenHandle)^^ DO BEGIN
			{restore the map, device, port}
			
			IF drawingPort <> NIL THEN BEGIN
				SetPort(drawingPort);
				drawingPort^.visRgn := savedVisRgn; {restore original visRgn}
				
				IF theMac.hasColorQD THEN
					SetPortPix(PMapHandlePtr(@savedMap)^)
				ELSE
					SetPortBits(savedMap);
			END; {for caller-supplied port case}				
				
			IF theMac.hasColorQD THEN
				SetGDevice(savedDevice);
				
			SetPort(savedPort);

			{we only need the pixmap locked while we are using it}
			IF theMac.hasColorQD THEN
				HUnlock(Handle(bitsPort^.portPixMap));

			IF NOT notOurs THEN
				{unlock the bits so they can float}
				HUnlock(bits);
			
			WITH bitsPort^ DO
				RectRgn(visRgn, portRect); {set private visRgn back to full size}
		END; {WITH}
END; {EndOffscreenDrawing}


PROCEDURE BeginUpdateOffscreen(offscreenHandle: Handle; window: WindowPtr);
BEGIN
	BeginUpdate(window);
	IF GetMap(offscreenHandle) <> NIL THEN BEGIN
		BeginOffscreenDrawing(offscreenHandle, window); {Õsects invalRegion with visRgn}
		{validate the entire contents of the offscreen buffer}
		ValidRectOffscreen(offscreenHandle, NIL, PrivateHandle(offscreenHandle)^^.bitsPort^.portRect);
	END;
END; {BeginUpdateOffscreen}


PROCEDURE EndUpdateOffscreen(offscreenHandle: Handle; window: WindowPtr);
BEGIN
	IF GetMap(offscreenHandle) <> NIL THEN BEGIN
		EndOffscreenDrawing(offscreenHandle);
		
		SetPort(window);
		ForeColor(blackColor);
		BackColor(whiteColor);
		{now copy from the offscreen buffer to the window (clipped to windowÕs visRgn)}
		WITH PrivateHandle(offscreenHandle)^^ DO
			CopyBits(GrafPtr(bitsPort)^.portBits, window^.portBits, bitsPort^.portRect,
				window^.portRect, srcCopy, NIL);
	END;
	EndUpdate(window);
END; {EndUpdateOffscreen}


{Create an off-screen pixel map for a port.
 It will always use kMaxDepth and always be memoryPolite.}
 
FUNCTION NewOffscreenForWindow(window: WindowPtr; VAR buffNotNeeded: BOOLEAN;
							   VAR offscreenHandle: Handle): OSErr;

VAR
	savedPort	: GrafPtr;
	globalRect	: Rect;

BEGIN {NewOffscreenForWindow}
	SetPort(window);

	globalRect := window^.portRect; {calculate a global rect for this window}
	WITH globalRect DO BEGIN
		LocalToGlobal(topLeft);
		LocalToGlobal(botRight);
	END;

	NewOffscreenForWindow := NewOffscreen(globalRect, kMaxDepth, NIL, TRUE,
		buffNotNeeded, offscreenHandle);
END; {NewOffscreenForWindow}



(* ??? looks like these arenÕt neededÉ
{Begin an update for the window specified, using the specified off-screen
 handle for bits. This sets the port to the window! Éand temporarily nukes
 the visRgn! ??? @@@ !!!}

PROCEDURE BeginOffscreenUpdate(offscreenHandle: Handle; window: WindowPtr);
BEGIN {BeginOffscreenUpdate}
	BeginOffscreenDrawing(offscreenHandle); {???!!! 
	SetPort(window); {override the offscreen port which was set by BOU}
	BeginUpdate(window);
	
END; {BeginOffscreenUpdate}



{Finish drawing to an off-screen port. Also copy the bits from the
 port to the screen.}

PROCEDURE EndOffscreenUpdate(offscreenHandle: Handle; window: WindowPtr);



{Call this when the rectangle of the off-screen map needs to change, perhaps
 due to a window moving on the main screen. This will also check for any change
 in depth.}

FUNCTION UpdateOffscreenForWindow(offscreenHandle: Handle; window: WindowPtr;
								  VAR drawNeeded: BOOLEAN): OSErr;
*)


END. {Offscreen}
